﻿using System;
using System.Security;
using Microsoft.Practices.Unity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace TestByWire
{
    public class Then
    {
        [ThreadStatic]
        internal protected static IUnityContainer Container;

        [ThreadStatic]
        private static object target;

        internal static object Target
        {
            get
            {
                return target;
            }
            set
            {
                target = value;
            }
        }


        public static void Reset(IUnityContainer container)
        {
            _firstVerificationMade = false;
            Container = container;
        }

        [ThreadStatic]
        private static Boolean _firstVerificationMade;

        public TException TargetShouldThrow<TException>(Action action)
            where TException : Exception
        {
            AssertDoubleInvocations();
            try
            {
                action.Invoke();
            }
            catch (TException e)
            {
                return e;
            }

            throw new VerificationException(string.Format("Expected exception of type {0} did not get thrown", typeof(TException).FullName));
        }

        internal static void AssertDoubleInvocations()
        {
            if (_firstVerificationMade)
                throw new InvalidOperationException("Unit tests only contain one verification per test");

            _firstVerificationMade = true;
        }

        public void TargetShouldNotThrow<TException>(Action action) where TException : Exception
        {
            AssertDoubleInvocations();
            try
            {
                action.Invoke();
            }
            catch (TException ex)
            {
                throw new VerificationException(action.Method + " threw unexpected exception " + ex.GetType());
            }
        }
    }

    public class Then<T> : Then
    {
        private Then()
        {
        }

        public static void Should(Action<T> action)
        {
            Should(null, action);
        }

        public static void Should(String name, Action<T> action)
        {
            AssertDoubleInvocations();
            var instance = Container.Resolve<T>(name);
            instance.AssertWasCalled(action);
        }

        public static void ShouldNot(Action<T> action)
        {
            ShouldNot(null, action);
        }

        public static void ShouldNot(String name, Action<T> action)
        {
            AssertDoubleInvocations();
            var instance = Container.Resolve<T>(name);
            instance.AssertWasNotCalled(action);
        }

        public static void Fulfills(Func<T, bool> func)
        {
            Fulfills(null, func);
        }

        public static void Fulfills(String name, Func<T, bool> func)
        {
            AssertDoubleInvocations();
            var result = func.Invoke(Container.Resolve<T>(name));
            if (!result)
                throw new AssertFailedException("Expected true, but was false.");
        }
    }
}
